package oneD.t3dviewer;
/* JThreeD.java  A set of classes to parse, represent and display
                 tensegrity models using Java3D -- see comments at
                 Model3D.load(InputStream) for data file format description

   History:

   12 Jun 2008 Do not handle exceptions in load(), pass them on.
               Remove ThreeDClient:  messages probably are not useful
               anymore and should be done with subscribe pattern anyway.
               Do not close the InputStream in load().  Merge all
               reset functions into resetTransform().  Add reset() which
               puts things back in initial state and redraws.
   10 Jun 2008 Use new Java3DModel class instead of identically behaved
               JModel3D class which is no longer with us.
   09 Aug 2006 Take advantage of new member functions LineSegment.p1()
               and p2().
   14 Jun 2006 Move scene graph to JThreeD and extract JModel3D from this
               file to a separate file.  Fix comment for m_magX.
   12 Jun 2006 In To do, indicate reason screen resizing needs fixing.
               Remove needless java.net.* and java.awt.event.* imports.
   01 Jun 2006 Add resetMagnification().  Don't clip magnification values.
               Change addPoint3d() to addPoint() etc.  Adjust
               rotation magnification by the model magnification
               (slow it down as model gets bigger).
   08 Dec 2005 Allow struts to have three different colors.  Hard code
               equality of m_colors.length and m_appearances.length.
   14 Oct 2005 Simplify addMember() and addPoint3f() and make their
               code symmetric.
   04 Oct 2005 Remove m_bounds.
   30 Sep 2005 Displays models from files.
   21 Sep 2005 Magnification working.
   20 Sep 2005 Created from ThreeD.java.

   To do:

   Fix screen resizing -- crashes JVM with EXCEPTION_ACCESS_VIOLATION when
   app Frame is resized.  Only happens in Windows -- Linux works OK.
 */

import java.awt.*;
import java.io.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.universe.SimpleUniverse;

/**
 * Manage the way an instance of Java3DModel is drawn on Canvas. Much of the
 * rendering technique and geometry management is adapted from the Java3D 1.3.1
 * FourByFour example.
 * 
 * @author Bob Burkhardt
 * @see Java3DModel
 */
public class JThreeD extends Canvas3D {
	/**
	 * The model geometry data.
	 */
	Java3DModel m_md;
	/**
	 * Java3D Universe for the model.
	 */
	SimpleUniverse m_universe;
	/**
	 * Current Java3D scene graph.
	 */
	BranchGroup m_root;
	/**
	 * AWT Colors for scene graph.
	 */
	private final static Color3f m_colors[] = new Color3f[10];
	/**
	 * Java3D Appearances for scene graph.
	 */
	private static Appearance m_appearances[];
	/**
	 * AWT color for background of Java3D scene graph.
	 */
	private final static Color3f BACKGROUND_COLOR = new Color3f(0.6f, 0.6f,
			0.6f);
	/**
	 * AWT color for Java3D ambient light for scene graph.
	 */
	private final static Color3f AMBIENT_LIGHT_COLOR = new Color3f(0.6f, 0.6f,
			0.6f);
	/**
	 * AWT color for Java3D directional light for scene graph.
	 */
	private final static Color3f DIRECTIONAL_LIGHT_COLOR = new Color3f(0.4f,
			0.4f, 0.4f);
	/**
	 * Java3D directional light direction for scene graph.
	 */
	private final static Vector3f DIRECTIONAL_LIGHT_DIRECTION = new Vector3f(
			0.3f, 0.2f, -1.0f);
	/**
	 * AWT color for Java3D emmissive color for scene graph.
	 */
	private final static Color3f EMISSIVE_COLOR = new Color3f(Color.black);
	/**
	 * AWT color for Java3D specular color for scene graph.
	 */
	private final static Color3f SPECULAR_COLOR = new Color3f(Color.white);
	/**
	 * Margin adjustment for the base scale value so there's a little space
	 * around the model at magnification 1.0.
	 */
	final static double MARGIN_SCALE_ADJUST = 0.85;
	/**
	 * Maximum magnification value (specified by client).
	 */
	final double m_maxMagnification;
	/**
	 * Adjustment so dragging moves the front of the figure by more than the
	 * length of the drag by the given adjustment factor.
	 */
	final static double ROTATION_MAGNIFICATION_FACTOR = 3.0;
	/**
	 * The current magnification.
	 */
	double m_mag = 1.0;
	/**
	 * One of the three dynamic transformations. The magnification
	 * transformation. Applied after rotation and shift.
	 */
	TransformGroup m_magX;
	/**
	 * One of the three dynamic transformations. Rotation matrix which
	 * determines the orientation of the model display. Applied before shift and
	 * magnification.
	 */
	TransformGroup m_rot;
	/**
	 * One of the three dynamic transformations. An offset applied to the
	 * location of the model after any rotation and before magnification.
	 */
	TransformGroup m_shift;
	/**
	 * X coordinate of current shift vector. Ranges from -m_md.m_radius to
	 * m_md.m_radius. Applied after rotation and before magnification.
	 */
	double m_shiftX;
	/**
	 * Y coordinate of current shift vector. Ranges from -m_md.m_radius to
	 * m_md.m_radius. Applied after rotation and before magnification.
	 */
	double m_shiftY;
	/**
	 * Coordinates of last observed mouse position (only updated during drags).
	 */
	int m_x = 0, m_y = 0;
	/**
	 * Dimension of largest square fitting on the Canvas.
	 */
	int m_dim;
	/**
	 * Helpers for setting transformation values. Used by JThreeD and/or
	 * Java3DModel. Take a load off the garbage collector.
	 */
	Transform3D m_transform = new Transform3D();
	Transform3D m_transform2 = new Transform3D();
	Vector3d m_vec = new Vector3d();
	Matrix3d m_mat = new Matrix3d();

	/**
	 * Create an intance of this GUI component.
	 * 
	 * @param maxMagnification
	 *            How much magnification to allow (>= 1.0)
	 * @param width
	 *            width of component
	 * @param height
	 *            height of component
	 */
	public JThreeD(double maxMagnification, int width, int height) {
		super(SimpleUniverse.getPreferredConfiguration());
		m_md = new Java3DModel();
		m_dim = width < height ? width : height;
		m_maxMagnification = maxMagnification;
		m_universe = new SimpleUniverse(this);
		m_universe.getViewer().getView()
				.setProjectionPolicy(View.PARALLEL_PROJECTION);
		m_universe.getViewer().getView().setFrontClipDistance(-10.0);
		m_universe.getViewingPlatform().setNominalViewingTransform();
		if (m_appearances == null) {
			m_appearances = new Appearance[m_colors.length];
			Material material;
			for (int i = 0; i < m_colors.length; i++) {
				material = new Material(m_colors[i], EMISSIVE_COLOR,
						m_colors[i], SPECULAR_COLOR, 100.f);
				material.setLightingEnable(true);
				m_appearances[i] = new Appearance();
				m_appearances[i].setMaterial(material);
			}
		}
	}

	static {
		// member colors (ambient and diffuse)
		m_colors[0] = new Color3f(Color.black);
		m_colors[1] = new Color3f(Color.green); // strut
		m_colors[2] = new Color3f(Color.red);
		m_colors[3] = new Color3f(Color.blue);
		m_colors[4] = new Color3f(Color.red);
		m_colors[5] = new Color3f(Color.blue);
		m_colors[6] = new Color3f(Color.black);
		m_colors[7] = new Color3f(Color.white);
		m_colors[8] = new Color3f(Color.orange); // strut
		m_colors[9] = new Color3f(Color.yellow); // strut
	}

	/**
	 * Initialize the model geometry from data contained in a stream. Set
	 * scaling.
	 * 
	 * @exception IOException
	 *                Input problem with the stream of model data.
	 * @exception Model3D.FileFormatException
	 *                Format problem with the stream.
	 * @exception Model3D.ModelException
	 *                Problem storing model in Model3D.
	 */
	public void load(InputStream is) throws IOException,
			Model3D.FileFormatException, Model3D.ModelException {
		m_md.load(is);
		createSceneGraph();
		resetTransform();
	}

	/**
	 * Set initial mouse coordinates for start of mouse-drag rotation or shift.
	 * 
	 * @param x
	 *            x coordinate of mouse
	 * @param y
	 *            y coordinate of mouse
	 */
	public void startDrag(int x, int y) {
		m_x = x;
		m_y = y;
	}

	/**
	 * Reset transform to initial state.
	 */
	private void resetTransform() {
		// magnification
		setMagnification(1.0);

		// rotation
		m_transform.setIdentity();
		m_rot.setTransform(m_transform);

		// shift
		setShift(0.0f, 0.0f);
	}

	/**
	 * Reset transform and model to initial state.
	 */
	public void reset() {
		resetTransform();
		m_md.reset();
		createSceneGraph();
	}

	/**
	 * Increment the rotation depending on new mouse coordinates.
	 * 
	 * @param x
	 *            x coordinate of mouse
	 * @param y
	 *            y coordinate of mouse
	 */
	public void updateRotation(int x, int y) {
		if (x == m_x && y == m_y)
			return;
		double deltax = 2.0 * (x - m_x) / m_dim;
		double deltay = -2.0 * (y - m_y) / m_dim;
		m_x = x;
		m_y = y;

		// angle of rotation (radians)
		double angle = Math.sqrt(deltax * deltax + deltay * deltay);
		// axis of rotation (normalized to length == 1; z == 0)
		double axis_x = -deltay / angle;
		double axis_y = deltax / angle;
		angle *= ROTATION_MAGNIFICATION_FACTOR / m_mag;

		// precompute sin, cos, products
		double sinangle = Math.sin(angle);
		double cosangle = Math.cos(angle);
		double x2 = axis_x * axis_x;
		double y2 = axis_y * axis_y;
		double xy = axis_x * axis_y;

		// update rotation matrix (Rogers67)
		m_mat.setElement(0, 0, x2 + (1.0 - x2) * cosangle);
		m_mat.setElement(0, 1, xy * (1.0 - cosangle));
		m_mat.setElement(0, 2, axis_y * sinangle);
		m_mat.setElement(1, 0, xy * (1.0 - cosangle));
		m_mat.setElement(1, 1, y2 + (1.0 - y2) * cosangle);
		m_mat.setElement(1, 2, -axis_x * sinangle);
		m_mat.setElement(2, 0, -axis_y * sinangle);
		m_mat.setElement(2, 1, axis_x * sinangle);
		m_mat.setElement(2, 2, cosangle);
		m_transform.set(m_mat);
		m_rot.getTransform(m_transform2);
		m_transform.mul(m_transform2);
		m_rot.setTransform(m_transform);
	}

	/**
	 * Set the magnification value.
	 * 
	 * @param value
	 *            magnification value
	 */
	public void setMagnification(double value) {
		if (m_magX != null) {
			m_transform.setIdentity();
			m_transform.setScale(value);
			m_magX.setTransform(m_transform);
			m_mag = value;
		}
	}

	/**
	 * Set the shift vector. Applied after rotation, but before magnification.
	 * 
	 * @param x
	 *            x component of shift vector
	 * @param y
	 *            y component of shift vector (each clipped to between
	 *            -m_md.m_radius and m_md.m_radius)
	 */
	public void setShift(double x, double y) {
		if (x < -m_md.m_radius)
			x = -m_md.m_radius;
		else if (x > m_md.m_radius)
			x = m_md.m_radius;
		if (y < -m_md.m_radius)
			y = -m_md.m_radius;
		else if (y > m_md.m_radius)
			y = m_md.m_radius;
		m_shiftX = x;
		m_shiftY = y;
		m_transform.setIdentity();
		m_vec.set(m_shiftX, m_shiftY, 0.0);
		m_transform.setTranslation(m_vec);
		m_shift.setTransform(m_transform);
	}

	/**
	 * Update the shift depending on new mouse coordinates and the current
	 * magnification.
	 * 
	 * @param x
	 *            x coordinate of mouse
	 * @param y
	 *            y coordinate of mouse
	 */
	public void updateShift(int x, int y) {
		if (x == m_x && y == m_y)
			return;

		double shiftx = 2.0 * (x - m_x) / (m_dim * m_mag) + m_shiftX;
		double shifty = -2.0 * (y - m_y) / (m_dim * m_mag) + m_shiftY;
		m_x = x;
		m_y = y;

		setShift(shiftx, shifty);
	}

	/**
	 * Create a scene graph for the current model.
	 */
	void createSceneGraph() {
		BranchGroup root = new BranchGroup();
		root.setCapability(BranchGroup.ALLOW_DETACH);

		BoundingSphere bounds = new BoundingSphere(new Point3d(),
				2.0 * m_maxMagnification);

		// background color
		Background background = new Background(BACKGROUND_COLOR);
		background.setApplicationBounds(bounds);
		root.addChild(background);

		// ambient light
		AmbientLight ambientLight = new AmbientLight(AMBIENT_LIGHT_COLOR);
		ambientLight.setInfluencingBounds(bounds);
		root.addChild(ambientLight);

		// directional light
		DirectionalLight directionalLight = new DirectionalLight(
				DIRECTIONAL_LIGHT_COLOR, DIRECTIONAL_LIGHT_DIRECTION);
		directionalLight.setInfluencingBounds(bounds);
		root.addChild(directionalLight);

		// transforms applying to the whole structure (organized like
		// a linked list)

		// magnification transform
		m_magX = new TransformGroup();
		m_magX.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		root.addChild(m_magX);

		// shift transform
		m_shift = new TransformGroup();
		m_shift.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		m_magX.addChild(m_shift);

		// rotation transform
		m_rot = new TransformGroup();
		m_rot.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
		m_rot.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		m_shift.addChild(m_rot);

		// scale model to fit in picture
		TransformGroup scale = new TransformGroup();
		scale.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		m_transform.setIdentity();
		m_transform.setScale(JThreeD.MARGIN_SCALE_ADJUST / m_md.m_radius);
		scale.setTransform(m_transform);
		m_rot.addChild(scale);

		// translate the model so it's centered on the origin
		TransformGroup translate = new TransformGroup();
		translate.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		m_transform.setIdentity();
		m_vec.sub(m_md.m_origin, m_md.m_center);
		m_transform.setTranslation(m_vec);
		translate.setTransform(m_transform);
		scale.addChild(translate);

		// and finally, the structure itself

		// find average strut length and use to set strut and tendon radii
		float avgsl = 0;
		int nstruts = 0;
		for (int i = 0; i < m_md.m_nlineSegments; i++) {
			Java3DModel.LineSegment lineSegment = m_md.m_lineSegments[i];
			if (lineSegment.m_type % 8 == 1) { // a strut
				m_vec.sub(lineSegment.p2(), lineSegment.p1());
				avgsl += m_vec.length();
				nstruts++;
			}
		}
		if (nstruts > 0)
			avgsl /= nstruts;
		else
			avgsl = 3.0f;
		float sradius = avgsl / 60;
		float tradius = sradius / 5;

		// create members
		for (int i = 0; i < m_md.m_nlineSegments; i++) {
			Java3DModel.LineSegment lineSegment = m_md.m_lineSegments[i];
			Point3d p1 = lineSegment.p1();
			m_vec.sub(lineSegment.p2(), p1);
			double mradius = (lineSegment.m_type % 8 == 1) ? sradius : tradius;
			double mlength = m_vec.length();
			Shape3D shape;
			if (lineSegment.m_type % 8 == 1) { // a strut
				// map strut type to appearance index
				int strut_type = (lineSegment.m_type - 1) / 8 + 1;
				if (strut_type > 3)
					strut_type = 3;
				if (strut_type > 1)
					strut_type += 6;

				// create strut
				Strut strut = new Strut(mradius, mlength, 12, 0.6f,
						m_appearances[strut_type]);
				shape = strut.getShape();
			} else { // a tendon
				Cylinder cyl = new Cylinder(mradius, mlength, 12,
						m_appearances[lineSegment.m_type % 8]);
				shape = cyl.getShape();
			}

			/*
			 * move member from default position and orientation (starts at
			 * origin and stretches along the x-axis) to structure position and
			 * orientation
			 */

			// create a transformation
			TransformGroup member_xform = new TransformGroup();
			member_xform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

			// orient the member
			double phi = -Math.asin(m_vec.z / mlength);
			double theta = Math.atan2(m_vec.y, m_vec.x);
			m_transform2.rotY(phi); // this is applied first
			m_transform.rotZ(theta); // then this
			m_transform.mul(m_transform2);

			// move the member into position
			m_vec.set(p1);
			m_transform.setTranslation(m_vec);

			// install the transformation
			member_xform.setTransform(m_transform);
			member_xform.addChild(shape);

			// add the member to the model
			translate.addChild(member_xform);
		}

		if (m_root != null)
			m_root.detach();
		m_root = root;
		m_universe.addBranchGraph(m_root);
	}

	public void destroy() {
		m_universe.cleanup();
	}

}
